ML en cancelaciones de reservas online de hoteles
Máster universitario en Ciencia de datos (Data science)
Estudios de Informática, Multimedia y Telecomunicación
El avance de la tecnología y el crecimiento de datos en tiempo real han llevado a la necesidad de desarrollar nuevas técnicas y modelos en el campo de la inteligencia artificial. La IA tradicional sigue un proceso estructurado que requiere grandes volúmenes de datos y tiempo para entrenamiento, pero no puede funcionar eficazmente en un mundo donde los datos cambian constantemente y se requieren respuestas casi instantáneas. La solución radica en el análisis de datos en tiempo real y en la creación de algoritmos y arquitecturas que permitan a la IA aprender de forma incremental cada vez que se disponga de nuevos datos.
El aprendizaje automático en línea presenta el problema del concept drift cuando el entorno y los datos cambian constantemente, lo que requiere el desarrollo de algoritmos y arquitecturas que permitan el aprendizaje incremental y el manejo del concept drift. La analítica de datos en tiempo real es esencial para mantener el modelo actualizado y adaptado a los cambios del entorno, mejorando su precisión y rendimiento.
Se ha desarrollado un proyecto para prever y anticiparse a los eventos de cancelación de reservas en páginas web de reservas de hoteles. El objetivo principal es predecir qué perfil de cliente es más propenso a cancelar sus reservas y cuál no, a través del análisis de la demanda de los clientes con un conjunto de datos. Además, se han utilizado métodos diferentes a los habituales para abordar el efecto del concept drift y mantener la efectividad del modelo a medida que los datos cambian.
Carga de librerías
import time
import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib import pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score, KFold
from sklearn.metrics import accuracy_score, confusion_matrix, make_scorer, roc_auc_score, precision_score, recall_score, f1_score
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
# Concept drift
from river.drift import ADWIN, KSWIN, PageHinkley
from river import metrics
from river import stream
from river import forest
from river import ensemble
%matplotlib inline
# Cargar datos desde un archivo CSV
df = pd.read_csv('HotelReservations.csv')
# Ver información básica del DataFrame
df.info()
# Ver las primeras filas de los datos
df.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 36275 entries, 0 to 36274 Data columns (total 19 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Booking_ID 36275 non-null object 1 no_of_adults 36275 non-null int64 2 no_of_children 36275 non-null int64 3 no_of_weekend_nights 36275 non-null int64 4 no_of_week_nights 36275 non-null int64 5 type_of_meal_plan 36275 non-null object 6 required_car_parking_space 36275 non-null int64 7 room_type_reserved 36275 non-null object 8 lead_time 36275 non-null int64 9 arrival_year 36275 non-null int64 10 arrival_month 36275 non-null int64 11 arrival_date 36275 non-null int64 12 market_segment_type 36275 non-null object 13 repeated_guest 36275 non-null int64 14 no_of_previous_cancellations 36275 non-null int64 15 no_of_previous_bookings_not_canceled 36275 non-null int64 16 avg_price_per_room 36275 non-null float64 17 no_of_special_requests 36275 non-null int64 18 booking_status 36275 non-null object dtypes: float64(1), int64(13), object(5) memory usage: 5.3+ MB
| Booking_ID | no_of_adults | no_of_children | no_of_weekend_nights | no_of_week_nights | type_of_meal_plan | required_car_parking_space | room_type_reserved | lead_time | arrival_year | arrival_month | arrival_date | market_segment_type | repeated_guest | no_of_previous_cancellations | no_of_previous_bookings_not_canceled | avg_price_per_room | no_of_special_requests | booking_status | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | INN00001 | 2 | 0 | 1 | 2 | Meal Plan 1 | 0 | Room_Type 1 | 224 | 2017 | 10 | 2 | Offline | 0 | 0 | 0 | 65.00 | 0 | Not_Canceled |
| 1 | INN00002 | 2 | 0 | 2 | 3 | Not Selected | 0 | Room_Type 1 | 5 | 2018 | 11 | 6 | Online | 0 | 0 | 0 | 106.68 | 1 | Not_Canceled |
| 2 | INN00003 | 1 | 0 | 2 | 1 | Meal Plan 1 | 0 | Room_Type 1 | 1 | 2018 | 2 | 28 | Online | 0 | 0 | 0 | 60.00 | 0 | Canceled |
| 3 | INN00004 | 2 | 0 | 0 | 2 | Meal Plan 1 | 0 | Room_Type 1 | 211 | 2018 | 5 | 20 | Online | 0 | 0 | 0 | 100.00 | 0 | Canceled |
| 4 | INN00005 | 2 | 0 | 1 | 1 | Not Selected | 0 | Room_Type 1 | 48 | 2018 | 4 | 11 | Online | 0 | 0 | 0 | 94.50 | 0 | Canceled |
# Obtener estadísticas descriptivas de las variables númericas
print('Descripción estadística de las variables numéricas')
df.describe().transpose()
Descripción estadística de las variables numéricas
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| no_of_adults | 36275.0 | 1.844962 | 0.518715 | 0.0 | 2.0 | 2.00 | 2.0 | 4.0 |
| no_of_children | 36275.0 | 0.105279 | 0.402648 | 0.0 | 0.0 | 0.00 | 0.0 | 10.0 |
| no_of_weekend_nights | 36275.0 | 0.810724 | 0.870644 | 0.0 | 0.0 | 1.00 | 2.0 | 7.0 |
| no_of_week_nights | 36275.0 | 2.204300 | 1.410905 | 0.0 | 1.0 | 2.00 | 3.0 | 17.0 |
| required_car_parking_space | 36275.0 | 0.030986 | 0.173281 | 0.0 | 0.0 | 0.00 | 0.0 | 1.0 |
| lead_time | 36275.0 | 85.232557 | 85.930817 | 0.0 | 17.0 | 57.00 | 126.0 | 443.0 |
| arrival_year | 36275.0 | 2017.820427 | 0.383836 | 2017.0 | 2018.0 | 2018.00 | 2018.0 | 2018.0 |
| arrival_month | 36275.0 | 7.423653 | 3.069894 | 1.0 | 5.0 | 8.00 | 10.0 | 12.0 |
| arrival_date | 36275.0 | 15.596995 | 8.740447 | 1.0 | 8.0 | 16.00 | 23.0 | 31.0 |
| repeated_guest | 36275.0 | 0.025637 | 0.158053 | 0.0 | 0.0 | 0.00 | 0.0 | 1.0 |
| no_of_previous_cancellations | 36275.0 | 0.023349 | 0.368331 | 0.0 | 0.0 | 0.00 | 0.0 | 13.0 |
| no_of_previous_bookings_not_canceled | 36275.0 | 0.153411 | 1.754171 | 0.0 | 0.0 | 0.00 | 0.0 | 58.0 |
| avg_price_per_room | 36275.0 | 103.423539 | 35.089424 | 0.0 | 80.3 | 99.45 | 120.0 | 540.0 |
| no_of_special_requests | 36275.0 | 0.619655 | 0.786236 | 0.0 | 0.0 | 0.00 | 1.0 | 5.0 |
# Obtener estadísticas descriptivas de las variables númericas
print('Descripción estadística de las variables categóricas')
df[['Booking_ID', 'type_of_meal_plan', 'room_type_reserved', 'market_segment_type', 'booking_status']].astype('object').describe().transpose()
Descripción estadística de las variables categóricas
| count | unique | top | freq | |
|---|---|---|---|---|
| Booking_ID | 36275 | 36275 | INN00001 | 1 |
| type_of_meal_plan | 36275 | 4 | Meal Plan 1 | 27835 |
| room_type_reserved | 36275 | 7 | Room_Type 1 | 28130 |
| market_segment_type | 36275 | 5 | Online | 23214 |
| booking_status | 36275 | 2 | Not_Canceled | 24390 |
# Verificar si hay valores faltantes en el DataFrame
print(df.isnull().sum())
# Eliminar filas o columnas con valores faltantes
df = df.dropna()
Booking_ID 0 no_of_adults 0 no_of_children 0 no_of_weekend_nights 0 no_of_week_nights 0 type_of_meal_plan 0 required_car_parking_space 0 room_type_reserved 0 lead_time 0 arrival_year 0 arrival_month 0 arrival_date 0 market_segment_type 0 repeated_guest 0 no_of_previous_cancellations 0 no_of_previous_bookings_not_canceled 0 avg_price_per_room 0 no_of_special_requests 0 booking_status 0 dtype: int64
No hay ningún valor nulo.
# Contar los valores únicos en una columna
booking_status_vc = df['booking_status'].value_counts()
print(booking_status_vc)
# Crear un histograma de una columna
booking_status_vc.plot.bar()
plt.title('Booking Status')
plt.xlabel('Estado de reservas')
plt.ylabel('Cantidad de clientes')
# Mostrar el gráfico
plt.show()
booking_status Not_Canceled 24390 Canceled 11885 Name: count, dtype: int64
"booking_status" es una variable que indica el estado de las reservas de este conjunto de datos. En este caso, los datos muestran que se han registrado 36275 reservas en total. De esas reservas, 24390 no han sido canceladas y 11885 han sido canceladas.
Es importante destacar que esta información por sí sola no nos da la razón por la cual se han cancelado algunas de las reservas, pero puede ser útil para realizar análisis posteriores. En general, la variable "booking_status" puede ser una herramienta útil para cualquier empresa o entidad que maneje reservas de cualquier tipo, ya que permite hacer un seguimiento del éxito o fracaso de las mismas y tomar decisiones en consecuencia.
El análisis de datos es una herramienta valiosa para entender y mejorar diversos procesos, y la industria hotelera no es una excepción. Una de las áreas donde el análisis de datos ha tenido un gran impacto es en la gestión de reservas de alojamiento. Con el creciente número de plataformas de reservas de hoteles en línea, se ha vuelto cada vez más importante entender los patrones de comportamiento de los clientes y anticiparse a posibles problemas, como cancelaciones de reservas.
En este sentido, el análisis de datos puede ayudar a los hoteles a prever las cancelaciones de reservas, identificar patrones de comportamiento de los clientes y tomar medidas para reducir su impacto. En particular, el análisis de datos puede ayudar a predecir qué perfil de cliente es más propenso a cancelar su reserva y en qué momento, lo que puede ser útil para optimizar la gestión de inventario, ajustar las políticas de cancelación y mejorar la satisfacción del cliente.
En este apartado, se abordará el análisis de datos aplicado a la cancelación de reservas en línea de hoteles, con un enfoque en cómo los datos pueden ser utilizados para predecir cancelaciones y reducir su impacto. Se presentarán los principales resultados y conclusiones de un estudio realizado sobre la base de datos de reservas de un hotel, y se discutirán las implicaciones y posibles aplicaciones de los hallazgos.
# Representación de las variables categóricas
fig, ax = plt.subplots(1, 3, figsize = (20, 4))
ax[0].bar(df['type_of_meal_plan'].value_counts().index, df['type_of_meal_plan'].value_counts())
ax[1].bar(df['room_type_reserved'].value_counts().index, df['room_type_reserved'].value_counts())
ax[2].bar(df['market_segment_type'].value_counts().index, df['market_segment_type'].value_counts())
ax[0].set_title('type_of_meal_plan')
ax[1].set_title('room_type_reserved')
ax[2].set_title('market_segment_type')
plt.show()
Se han representado las variables categóricas "type_of_meal_plan", "room_type_reserved" y "market_segment_type". No se ha representado "Booking_ID", ya que esta variable tiene 36275 registros diferentes, por lo que no se puede sacar ninguna conclusión y no es muy válido para el análisis de datos.
# Representación de las variables numéricas
fig1, ax1 = plt.subplots(1, 5, figsize = (20, 3))
fig2, ax2 = plt.subplots(1, 5, figsize = (20, 3))
fig3, ax3 = plt.subplots(1, 4, figsize = (16, 3))
ax1[0].bar(df['no_of_adults'].value_counts().index, df['no_of_adults'].value_counts())
ax1[1].bar(df['no_of_children'].value_counts().index, df['no_of_children'].value_counts())
ax1[2].bar(df['no_of_weekend_nights'].value_counts().index, df['no_of_weekend_nights'].value_counts())
ax1[3].bar(df['no_of_week_nights'].value_counts().index, df['no_of_week_nights'].value_counts())
ax1[4].bar(df['required_car_parking_space'].value_counts().index, df['required_car_parking_space'].value_counts())
ax2[0].hist(df['lead_time'])
ax2[1].bar(df['arrival_year'].value_counts().index, df['arrival_year'].value_counts())
ax2[2].bar(df['arrival_month'].value_counts().index, df['arrival_month'].value_counts())
ax2[3].hist(df['arrival_date'])
ax2[4].bar(df['repeated_guest'].value_counts().index, df['repeated_guest'].value_counts())
ax3[0].bar(df['no_of_previous_cancellations'].value_counts().index, df['no_of_previous_cancellations'].value_counts())
ax3[1].hist(df['no_of_previous_bookings_not_canceled'])
ax3[2].bar(df['avg_price_per_room'].value_counts().index, df['avg_price_per_room'].value_counts())
ax3[3].bar(df['no_of_special_requests'].value_counts().index, df['no_of_special_requests'].value_counts())
ax1[0].set_title('no_of_adults')
ax1[1].set_title('no_of_children')
ax1[2].set_title('no_of_weekend_nights')
ax1[3].set_title('no_of_week_nights')
ax1[4].set_title('required_car_parking_space')
ax2[0].set_title('lead_time')
ax2[1].set_title('arrival_year')
ax2[2].set_title('arrival_month')
ax2[3].set_title('arrival_date')
ax2[4].set_title('repeated_guest')
ax3[0].set_title('no_of_previous_cancellations')
ax3[1].set_title('no_of_previous_bookings_not_canceled')
ax3[2].set_title('avg_price_per_room')
ax3[3].set_title('no_of_special_requests')
plt.show()
Muchos de estos gráficos ratifican los datos estadísticos que se han analizado en el apartado anterior. Como las variables 'no_of_adults', 'no_of_children', 'no_of_week_nights', 'required_car_parking_space', 'arrival_year', 'arrival_date', 'repeated_guest', 'no_of_previous_cancellations' y 'no_of_previous_bookings_not_canceled'. Sin embargo, en las otras variables se han sacado más datos y conclusiones:
# Representación de las variables en relación a 'booking_status'
plt.figure(figsize = (20, 25))
plt.suptitle("Análisis de la variable 'booking_status'",fontweight="bold", fontsize=20)
plt.subplot(5,2,1)
sns.countplot(x = 'booking_status', hue = 'no_of_adults', palette = 'Set2', data = df)
plt.subplot(5,2,2)
sns.countplot(x = 'booking_status', hue = 'no_of_children', palette = 'Set2', data = df)
plt.subplot(5,2,3)
sns.countplot(x = 'booking_status', hue = 'no_of_weekend_nights', palette = 'Set2', data = df)
plt.subplot(5,2,4)
sns.countplot(x = 'booking_status', hue = 'required_car_parking_space', palette = 'Set2', data = df)
plt.subplot(5,2,5)
sns.countplot(x = 'booking_status', hue = 'market_segment_type', palette = 'Set2', data = df)
plt.subplot(5,2,6)
sns.countplot(x = 'booking_status', hue = 'type_of_meal_plan', palette = 'Set2', data = df)
plt.subplot(5,2,7)
sns.countplot(x = 'booking_status', hue = 'room_type_reserved', palette = 'Set2', data = df)
plt.subplot(5,2,8)
sns.countplot(x = 'booking_status', hue = 'repeated_guest', palette = 'Set2', data = df)
plt.subplot(5,2,9)
sns.countplot(x = 'booking_status', hue = 'no_of_special_requests', palette = 'Set2', data = df)
plt.show()
Se han representado las variables 'no_of_adults', 'no_of_children', 'no_of_weekend_nights', 'required_car_parking_space', 'market_segment_type', 'type_of_meal_plan', 'room_type_reserved' y 'no_of_special_requests' en función de la variable 'booking_status' para ver el comportamiento de las variables y ver si se pueden sacar diferentes conclusiones.
Se han relacionado diferentes variables entre ellas en relación a la variable objetivo ('booking_status') porque se busca entender cómo estas variables se interrelacionan y cómo pueden influir en 'booking_status'. Al analizar las relaciones entre las variables, se han podido identificar patrones, dependencias y asociaciones que nos ayudan a comprender mejor el fenómeno que han estado estudiando. Para ello se ha creado varios gráficos para visualizar estas relaciones.
# Representación de las relaciones entre variables
fig, ax = plt.subplots(2,3, figsize = (20, 20))
plot1 = ax[0][0].scatter(df['lead_time'].index, df['lead_time'], c=df['booking_status'].map({'Canceled': 'red', 'Not_Canceled':'blue'}))
ax[0][0].set_title('Días antes de llegada al hotel')
ax[0][0].set_ylabel('Cantidad de días')
plot2 = ax[0][1].scatter(df['no_of_adults'], df['no_of_week_nights'], c=df['booking_status'].map({'Canceled': 'red', 'Not_Canceled':'blue'}))
ax[0][1].set_title('Número de adultos por noche')
ax[0][1].set_xlabel('Cantidad de adultos')
ax[0][1].set_ylabel('Noches')
plot3 = ax[0][2].scatter(df['arrival_month'], df['lead_time'], c=df['booking_status'].map({'Canceled': 'red', 'Not_Canceled':'blue'}))
ax[0][2].set_title('Días antes de llegada al hotel por meses')
ax[0][2].set_xlabel('Meses')
ax[0][2].set_ylabel('Cantidad de días')
plot4 = ax[1][0].scatter(df['arrival_month'], df['avg_price_per_room'], c=df['booking_status'].map({'Canceled': 'red', 'Not_Canceled':'blue'}))
ax[1][0].set_title('Precio medio por mes')
ax[1][0].set_xlabel('Meses')
ax[1][0].set_ylabel('Euros')
plot5 = ax[1][1].scatter(df['room_type_reserved'], df['avg_price_per_room'], c=df['booking_status'].map({'Canceled': 'red', 'Not_Canceled':'blue'}))
ax[1][1].set_title('Precio medio por tipo de habitación')
ax[1][1].set_ylabel('Euros')
plot6 = ax[1][2].scatter(df['no_of_special_requests'], df['avg_price_per_room'], c=df['booking_status'].map({'Canceled': 'red', 'Not_Canceled':'blue'}))
ax[1][2].set_title('Precio de habitaciones con peticiones especiales')
ax[1][2].set_xlabel('Cantidad de peticiones especiales')
ax[1][2].set_ylabel('Euros')
leg = plt.figlegend((plot1, plot2, plot3, plot4, plot5), ['Canceled', 'Not_Canceled'], loc='upper center', ncol=1, title='booking_status')
leg.legend_handles[0].set_color('red')
leg.legend_handles[1].set_color('blue')
plt.show()
Las variables categóricas revelaron que la mayoría de los huéspedes eligen el 'Meal Plan 1', las habitaciones más reservadas son del tipo 1 y 4, y la mayoría de las reservas provienen del mercado en línea. En cuanto a otras variables, se observó que la mayoría de los clientes no reservan noches de fin de semana, las reservas se realizan con pocos días de antelación y los meses con más llegadas al hotel son agosto, septiembre y octubre. El precio medio de las habitaciones es variable, pero la mayoría se encuentra por debajo de 100€, y la mayoría de los huéspedes no hacen peticiones especiales.
Al relacionar estas variables con el estado de reserva, se encontró que las reservas con tres adultos y las que incluyen niños tienen mayor probabilidad de cancelarse. Las reservas que incluyen noches de fin de semana también tienen más probabilidades de ser canceladas. Los huéspedes que no reservan estacionamiento y los del mercado en línea tienen una mayor tasa de cancelación. Aquellos que eligen el 'Meal Plan 2' son más propensos a cancelar, y las habitaciones tipo 1 y 4 tienen una mayor tasa de cancelación.
En general, estos resultados brindan información valiosa para la gestión hotelera y la toma de decisiones relacionadas con servicios y precios.
# Creación de la variable 'no_of_guests' --> Cantidad de huéspedes en total
df['no_of_guests'] = df['no_of_adults'] + df['no_of_children']
# Creación de la variable 'no_of_nights' --> Cantidad de noches en total
df['no_of_nights'] = df['no_of_weekend_nights'] + df['no_of_week_nights']
# Creación de la fecha de llegada con todos los datos
def create_date_string(row):
return f"{row['arrival_year']:04}{row['arrival_month']:02}{row['arrival_date']:02}"
dates = df[['arrival_year', 'arrival_month', 'arrival_date']]
df['date'] = dates.apply(create_date_string, axis=1)
df['date'] = df['date'].astype(int)
Se ha decidido crear dos variables nuevas que se llaman 'no_of_guests' y 'no_of_nights' para simplificar el conjunto de datos sin perder información, ya que las variables nuevas se crean en base a variables existentes. Como, 'no_of_guests' que representa el número de total de huéspedes y es creada con la suma de las variables 'no_of_adults' y 'no_of_children'. Por otro lado, 'no_of_nights' representa el número total de noches que se ha reservado el alojamiento. Esto se consigue con la suma de las variables 'no_of_weekend_nights' y 'no_of_week_nights'. Por otro lado, se han unificado las variables 'arrival_year', 'arrival_month' y 'arrival_date' para crear la nueva variable 'date. Esta variable 'date' es una variable númerica que determina la fecha de llegada del huésped. Esta variable numérica tiene toda la información que tiene las tres variables mencionadas y tiene el siguiente formato 'AñoMesDía', por ejemplo, el 2 de octubre de 2018 estaría compuesto de la siguiente manera, 20181002.
# Ajustar y transformar la lista de valores categóricos
df['type_of_meal_plan'] = df['type_of_meal_plan'].map({"Meal Plan 1": 1, 'Not Selected': 0, 'Meal Plan 2': 2 ,'Meal Plan 3': 3})
df['room_type_reserved'] = df["room_type_reserved"].map({'Room_Type 1': 1, 'Room_Type 2': 2, 'Room_Type 3': 3,'Room_Type 4': 4,'Room_Type 5': 5,'Room_Type 6': 6,'Room_Type 7': 7})
df['market_segment_type'] = df['market_segment_type'].map({"Offline": 0, 'Online': 1, 'Corporate': 2 ,'Aviation': 3,'Complementary': 4})
df['booking_status'] = df['booking_status'].map({'Canceled': 0, 'Not_Canceled': 1})
df['type_of_meal_plan'] = df['type_of_meal_plan'].astype(int)
df['room_type_reserved']= df['room_type_reserved'].astype(int)
df['market_segment_type']= df['market_segment_type'].astype(int)
df['booking_status']= df['booking_status'].astype(int)
# Extraer 'booking_status'
col = df.pop('booking_status')
# Añadir la 'booking_status' al final del DataFrame
df.insert(len(df.columns), 'booking_status', col)
hotels = df.drop(['Booking_ID', 'no_of_adults', 'no_of_children', 'no_of_weekend_nights', 'no_of_week_nights',\
'arrival_year', 'arrival_month', 'arrival_date'], axis=1)
hotels.head()
| type_of_meal_plan | required_car_parking_space | room_type_reserved | lead_time | market_segment_type | repeated_guest | no_of_previous_cancellations | no_of_previous_bookings_not_canceled | avg_price_per_room | no_of_special_requests | no_of_guests | no_of_nights | date | booking_status | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 1 | 224 | 0 | 0 | 0 | 0 | 65.00 | 0 | 2 | 3 | 20171002 | 1 |
| 1 | 0 | 0 | 1 | 5 | 1 | 0 | 0 | 0 | 106.68 | 1 | 2 | 5 | 20181106 | 1 |
| 2 | 1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 60.00 | 0 | 1 | 3 | 20180228 | 0 |
| 3 | 1 | 0 | 1 | 211 | 1 | 0 | 0 | 0 | 100.00 | 0 | 2 | 2 | 20180520 | 0 |
| 4 | 0 | 0 | 1 | 48 | 1 | 0 | 0 | 0 | 94.50 | 0 | 2 | 2 | 20180411 | 0 |
Las variables categóricas 'type_of_meal_plan', 'room_type_reserved' y 'market_segment_type', se han convertido a variables númericas mediante mapeos. Los mapeos intercambian los valores categóricos por valores determinados, después se convierte ese valor a númerico, ya que el formato es compatible. Por ejemplo, 'market_segment_type' tiene los siguientes valores categóricos: 'Offline', 'Online', 'Aviation', 'Corporate' y 'Complementary'. Estos se han convertido en los valores 0, 1, 2, 3 y 4, respectivamente. Otro ejemplo claro es la variable 'booking_status', cuando uno (1) es valor 'Not_Canceled' y cero (0) es 'Canceled'.
Por otro lado, se han eliminado las variables innecesarias para el análisis de datos, ya que son variables que no aportan información. Como, la variable 'Booking_ID' que son 36275 identificadores diferentes, por lo que no se puede encontrar ningún patron diferencial. También, se han eliminado las variables 'no_of_adults', 'no_of_children', 'no_of_nights'no_of_weekend_nights' y 'no_of_week_nights' por la creación de las variables mencionadas en el párrafo anterior que ya dan la información que dan estas variables. Por último, se han eliminado 'arrival_year', 'arrival_month' y 'arrival_date', porque se ha creado una nueva variable 'date' que engloba los datos de estas tres variables.
plt.figure(figsize = (14,7))
sns.heatmap(hotels.corr().round(2), annot = True, cmap = 'coolwarm')
<Axes: >
Los valores de correlación varían entre -1 y 1, donde -1 indica una correlación negativa perfecta, 0 indica una correlación nula, y 1 indica una correlación positiva perfecta.
En general, los valores de correlación más fuertes son aquellos que están más cerca de -1 o 1. Una correlación negativa perfecta (valor -1) indica una relación lineal inversa perfecta, lo que significa que cuando una variable aumenta, la otra disminuye en una forma predecible. Por otro lado, una correlación positiva perfecta (valor 1) indica una relación lineal directa perfecta, lo que significa que cuando una variable aumenta, la otra también aumenta en una forma predecible.
Sin embargo, es importante tener en cuenta que la interpretación de la fuerza de la correlación puede depender del contexto del problema y del dominio de los datos en estudio. En algunos casos, una correlación moderada (por ejemplo, entre 0.3 y 0.7) puede considerarse fuerte, como es en este caso. Viendo el mapa de calor se ha examinado las correlaciones más fuertes entre diferentes variables:
Por otro lado, estas son las variables que tiene correlación más fuerte con 'booking_status'. booking_status - lead_time: -0.44 - 'lead_time' tiene una correlación negativa fuerte con la variable respuesta. Como se ha explicado anteriormente una correlación negativa indica una relación lineal inversa perfecta. En este caso, si la reserva se hace a pocos días de la llegada es probable que esta no se cancele. Por el contrario, si la se reserva se hace con mucho tiempo de antelación es más probable que esta se cancele. booking_status - no_of_special_requests: 0.25 - Esta es la segunda correlación más fuerte que tiene 'booking_status' con las demás variables. En este caso, cuantas más peticiones especiales se hagan es más probable de que la reserva no se cancele.
Al ver este mapa de calor se pensaba que otra variables podrían tener una correlación fuerte, sin embargo muchas de estas variables tienen correlaciones debiles.
La eliminación de variables con correlación débil es un paso importante en el proceso de modelado de datos, ya que nos permite simplificar el modelo y eliminar el ruido innecesario. Al eliminar estas variables, se ha enfocado en aquellas que realmente tienen una influencia significativa en la variable objetivo, lo que nos ayuda a mejorar la precisión y la interpretación de nuestro modelo.
Existen diferentes enfoques para identificar y eliminar variables con correlación débil. Uno de ellos es utilizar técnicas estadísticas, como el coeficiente de correlación o las pruebas de hipótesis, para evaluar la fuerza de la relación entre cada variable y la variable objetivo. Aquellas variables que presenten una correlación cercana a cero o un valor de p alto se considerarán como variables con correlación débil y se pueden eliminar del modelo.
# REDUCCIÓN DE DIMENSIONALIDAD / Variables con valor de correlación entre -0.1 y 0.1 se van a borrar
hotels_cleaned = hotels.drop(['type_of_meal_plan', 'room_type_reserved', 'no_of_previous_cancellations', 'no_of_previous_bookings_not_canceled'], axis=1)
# Ordenar las reservas por fecha
hotels_cleaned.sort_values('date', inplace=True)
hotels_cleaned.head()
| required_car_parking_space | lead_time | market_segment_type | repeated_guest | avg_price_per_room | no_of_special_requests | no_of_guests | no_of_nights | date | booking_status | |
|---|---|---|---|---|---|---|---|---|---|---|
| 3871 | 0 | 257 | 1 | 0 | 101.5 | 0 | 2 | 2 | 20170701 | 0 |
| 26642 | 0 | 257 | 0 | 0 | 101.5 | 0 | 2 | 2 | 20170701 | 0 |
| 16946 | 0 | 257 | 0 | 0 | 101.5 | 0 | 2 | 2 | 20170701 | 0 |
| 3898 | 0 | 257 | 1 | 0 | 101.5 | 0 | 2 | 2 | 20170701 | 0 |
| 5884 | 0 | 257 | 0 | 0 | 80.0 | 0 | 1 | 2 | 20170701 | 1 |
Las variables con una correlación cercana a cero generalmente tienen poca influencia en la relación entre las variables y pueden no proporcionar información significativa para el análisis o la construcción del modelo. Sin embargo, es importante tener en cuenta el contexto y la naturaleza del problema en particular antes de eliminar variables, ya que podría existir información relevante que no se refleje en la correlación. Además, es recomendable utilizar técnicas de selección de características más avanzadas que consideren otros factores, como la importancia predictiva y la contribución al rendimiento del modelo.
El análisis de los datos reveló interesantes relaciones que influyen en la experiencia del huésped en el hotel. En primer lugar, se encontró que el historial de cancelaciones y reservas anteriores juega un papel crucial en la condición de 'repeated_guest'. Aquellos que han tenido un alto número de cancelaciones previas tienen menos probabilidades de repetir, mientras que aquellos con un historial de reservas no canceladas son más propensos a volver.
Otra relación importante se observa entre el tipo de habitación y el número de huéspedes. Es evidente que las habitaciones más grandes tienden a acomodar a más huéspedes. Esto es lógico, ya que las preferencias de los huéspedes y la disponibilidad de espacio están estrechamente vinculadas. Los huéspedes suelen optar por habitaciones más grandes cuando viajan en grupos o con familias, lo que resulta en una mayor ocupación.
El precio promedio por habitación también está relacionado tanto con el tipo de habitación como con el número de huéspedes. Habitaciones más grandes y con mayor capacidad suelen tener un precio más alto. Esta relación es comprensible, ya que las habitaciones más espaciosas y lujosas ofrecen más comodidad y, por lo tanto, suelen tener un costo mayor.
El tiempo de antelación en la realización de una reserva también desempeña un papel importante en el estado de la misma. Se observó que las reservas realizadas con mayor antelación tienen una mayor probabilidad de ser canceladas. Esto podría atribuirse a cambios en los planes de los huéspedes, imprevistos o simplemente una mayor incertidumbre en el futuro.
Por último, las solicitudes especiales realizadas por los huéspedes también influyen en el estado de la reserva. Aquellos que presentan un mayor número de solicitudes especiales tienen una menor probabilidad de cancelación. Esto indica que los huéspedes que tienen necesidades o preferencias específicas están más comprometidos con su reserva y tienen menos probabilidades de cancelarla.
Ahora que el dataset está limpio y se ha hecho un análisis de las diferentes variables, se puede proceder a entrenar un modelo para predecir si se cancela la reserva o no.
Se probarán con 3 modelos diferentes (DecisionTreeClassifier, RandomForestClassifier y LogisticRegression) e se interpretará los resultados.
Para empezar, se separan los datos en las variables independientes o características (features) y la variable dependiente o 'booking_status' (target) que se quiere predecir.
Después, se utiliza para dividir los datos en conjuntos de entrenamiento y prueba, lo que permite evaluar la capacidad de generalización del modelo.
features = hotels_cleaned.loc[:, hotels_cleaned.columns != 'booking_status']
target = hotels_cleaned['booking_status']
X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.2, random_state=24)
# Hacer una instancia del modelo DecisionTreeClassifier
train_tree = DecisionTreeClassifier(max_depth = 5, random_state = 24).fit(X_train, y_train)
# Representación del árbol de decisión
plt.figure(figsize=(10, 8))
plot_tree(train_tree, feature_names=list(features.columns), fontsize=12)
# Calculando precisión y matriz de confusión del modelo de train
y_train_pred = train_tree.predict(X_train)
tree_train_accuracy = accuracy_score(y_train, y_train_pred)
print("Precisión de modelo train: {}".format(tree_train_accuracy))
print("Matriz de confusion de train:\n{}".format(confusion_matrix(y_train, y_train_pred)))
# Calculando precisión y matriz de confusión del modelo de test
y_test_pred = train_tree.predict(X_test)
tree_test_accuracy = accuracy_score(y_test, y_test_pred)
print("Precisión de modelo test: {}".format(tree_test_accuracy))
print("Matriz de confusion:\n{}".format(confusion_matrix(y_test, y_test_pred)))
Precisión de modelo train: 0.8330806340454858 Matriz de confusion de train: [[ 6787 2748] [ 2096 17389]] Precisión de modelo test: 0.8237077877325982 Matriz de confusion: [[1634 716] [ 563 4342]]
En este paso se evalúa el rendimiento del modelo utilizando diferentes métricas de evaluación, como la precisión y la matriz de confusión, tanto en el conjunto de entrenamiento como en el de prueba. Los resultados indican que el modelo tiene una precisión aceptable tanto en el conjunto de entrenamiento como en el de prueba, con valores de 0.829 y 0.833, respectivamente. Además, la matriz de confusión del conjunto de entrenamiento indica que el modelo clasifica correctamente la mayoría de los casos negativos (6591 verdaderos negativos) pero tiene más dificultades para identificar los casos positivos (2072 falsos negativos). La matriz de confusión del conjunto de prueba indica que el modelo tiene un desempeño similar en ambos tipos de casos, con 1632 verdaderos negativos y 4410 verdaderos positivos. En general, estos resultados sugieren que el modelo es capaz de clasificar con precisión la mayoría de los casos en ambos conjuntos de datos, aunque podría haber cierto sesgo en la predicción de casos positivos en el conjunto de entrenamiento.
# Grid de hiperparámetros evaluados
param_grid = {'max_depth': [4, 5, 7, 8, 10, 11]}
# Búsqueda por grid search con validación cruzada
grid = GridSearchCV(
estimator = RandomForestClassifier(random_state=24),
param_grid = param_grid
)
grid.fit(X_train, y_train)
print("El mejor conjunto de parámetros encontrado en el conjunto de desarrollo: {}".format(grid.best_params_))
# Hacer una instancia del modelo RandomForestClassifier
train_rfc = RandomForestClassifier(random_state = 24).fit(X_train, y_train)
# Calculando precisión y matriz de confusión del modelo de test
y_test_pred = train_rfc.predict(X_test)
rfc_test_accuracy = accuracy_score(y_test, y_test_pred)
print("Precisión de modelo test: {}".format(rfc_test_accuracy))
print("Matriz de confusion:\n{}".format(confusion_matrix(y_test, y_test_pred)))
El mejor conjunto de parámetros encontrado en el conjunto de desarrollo: {'max_depth': 11}
Precisión de modelo test: 0.8949689869055824
Matriz de confusion:
[[1888 462]
[ 300 4605]]
El código realiza una búsqueda de los mejores parámetros para un modelo RandomForestClassifier utilizando GridSearchCV y validación cruzada. GridSearchCV realiza una búsqueda exhaustiva de los mejores parámetros del modelo en una cuadrícula de posibles valores. Luego, se ajusta el modelo con los mejores parámetros encontrados y se hace una predicción en el conjunto de prueba.
Se puede decir que se ha obtenido una buena precisión en el conjunto de test, ya que la precisión del modelo es de 0.895, lo cual indica que el modelo clasifica correctamente el 89'5% de las instancias en el conjunto de test. Además, la matriz de confusión muestra que el modelo ha logrado predecir correctamente la mayoría de las instancias, con un bajo número de falsos positivos y falsos negativos. El mejor conjunto de parámetros encontrado por el modelo durante el entrenamiento es {'max_depth': 11}, lo que sugiere que el modelo ha logrado ajustarse adecuadamente a los datos de entrenamiento sin sobreajustar. En general, estos resultados indican que el modelo es adecuado para su uso en la clasificación de datos similares.
# Definir las etapas del modelo
model = Pipeline([
('scale', StandardScaler()),
('lin_reg', LogisticRegression(solver='lbfgs'))
])
model.fit(X_train, y_train)
# Calculando precisión y matriz de confusión del modelo de test
y_test_pred = model.predict(X_test)
lr_test_accuracy = accuracy_score(y_test, y_test_pred)
print("Precisión de modelo test: {}".format(lr_test_accuracy))
print("Matriz de confusion:\n{}".format(confusion_matrix(y_test, y_test_pred)))
Precisión de modelo test: 0.7937973811164714 Matriz de confusion: [[1368 982] [ 514 4391]]
La precisión de modelo test indica que este modelo ha logrado una precisión del 80'26% al predecir los resultados de un conjunto de datos de prueba. La matriz de confusión muestra el número de predicciones correctas e incorrectas realizadas por el modelo en el conjunto de datos de prueba. En la diagonal principal, se muestran las predicciones correctas: 1424 observaciones fueron clasificadas correctamente como negativas (la clase 0) y 4399 observaciones fueron clasificadas correctamente como positivas (la clase 1). En la diagonal secundaria, se muestran las predicciones incorrectas: 506 observaciones de la clase 0 fueron clasificadas como pertenecientes a la clase 1 (falsos positivos), mientras que 926 observaciones de la clase 1 fueron clasificadas como pertenecientes a la clase 0 (falsos negativos).
# Definir un procedimiento de validación cruzada determinista
cv = KFold(n_splits=5, shuffle=True, random_state=24)
# Calcular los valores MSE
scorer = make_scorer(roc_auc_score)
scores = cross_val_score(model, features, target, scoring=scorer, cv=cv)
scores_train_tree = cross_val_score(train_tree, features, target, scoring=scorer, cv=cv)
scores_train_rfc = cross_val_score(train_rfc, features, target, scoring=scorer, cv=cv)
# Mostrar la puntuación media y su desviación estándar
print(f'ROC AUC: {scores.mean():.3f} (± {scores.std():.3f})')
print(f'ROC AUC train_tree: {scores_train_tree.mean():.3f} (± {scores_train_tree.std():.3f})')
print(f'ROC AUC train_rfc: {scores_train_rfc.mean():.3f} (± {scores_train_rfc.std():.3f})')
ROC AUC: 0.736 (± 0.007) ROC AUC train_tree: 0.791 (± 0.003) ROC AUC train_rfc: 0.875 (± 0.003)
En este paso se evalúa el rendimiento del modelo utilizando la métrica de puntuación ROC AUC y se utiliza la validación cruzada para evaluar el rendimiento del modelo en diferentes subconjuntos de datos. Se calcula la puntuación ROC AUC en la validación cruzada de un modelo de aprendizaje automático. La puntuación ROC AUC mide la capacidad del modelo para distinguir entre clases positivas y negativas en un problema de clasificación binaria.
Un buen resultado sería una puntuación ROC AUC cercana a 1, lo que indica que el modelo tiene una alta capacidad para distinguir entre las clases positivas y negativas en un problema de clasificación binaria. Sin embargo, el nivel de "bueno" dependerá del contexto del problema y de las expectativas del usuario. En este caso, el resultado de 0'736 podría considerarse aceptable, pero podría mejorarse. Un ROC AUC de 0'791 indica que el modelo basado en árboles de decisión es ligeramente mejor que el modelo de regresión logística. Por último, el resultado de 0'851 indica que el modelo de RandomForestClassifier tiene un rendimiento considerablemente mejor que los otros modelos evaluados.
En el modelo DecisionTreeClassifier, se destaca que el modelo tiene una precisión aceptable tanto en el conjunto de entrenamiento como en el de prueba, aunque se observa una dificultad en la identificación de casos positivos. Esto sugiere que el modelo podría beneficiarse de mejoras en su capacidad para clasificar correctamente los casos positivos.
En el segundo modelo RandomForestClassifier, se muestra que el modelo ha obtenido una buena precisión (89'5%) en el conjunto de prueba, indicando que clasifica correctamente la mayoría de las instancias. Además, la matriz de confusión revela un bajo número de falsos positivos y falsos negativos, lo que sugiere un rendimiento efectivo del modelo en la clasificación de los datos.
Por otro lado, el tercer modelo LogisticRegression destaca que el modelo logra una precisión del 80'26% en la predicción de un conjunto de datos de prueba. Sin embargo, la matriz de confusión muestra la presencia de algunos falsos positivos y falsos negativos, lo que indica que el modelo comete errores en la clasificación de ciertos casos.
En resumen, los resultados sugieren que los modelos tienen un rendimiento generalmente aceptable en la clasificación de los datos, aunque existe margen para mejorar la identificación de casos positivos y reducir los errores de clasificación. En función de estos resultados, el RandomForestClassifier parece ser el modelo con el rendimiento más alto, con una precisión de 0'895 en el conjunto de prueba y una matriz de confusión que muestra un bajo número de errores de clasificación. También, tiene un área bajo la curva ROC más alta. Por lo tanto, considerando ambas métricas, el modelo de RandomForestClassifier es el mejor en esta evaluación. Entonces, viendo los resultados el modelo que se va a utilizar para hacer las predicciones será el modelo de RandomForestClassifier, ya que ha obtenido los mejores resultados comparando con los demás.
En este paso se obtiene la importancia de las diferentes características del modelo, lo que permite identificar las características más importantes para la predicción del objetivo. En este análisis se presenta la importancia de las características (features) en un modelo de clasificación que se ha elegido (RandomForestClassifier).
# Se obtiene la importancia de las diferentes características del modelo
feature_importance = pd.DataFrame({'feature': list(features.columns), 'importance': list(train_rfc.feature_importances_)})
feature_importance.sort_values('importance', ascending=False)
| feature | importance | |
|---|---|---|
| 1 | lead_time | 0.347151 |
| 8 | date | 0.200272 |
| 4 | avg_price_per_room | 0.192121 |
| 5 | no_of_special_requests | 0.095028 |
| 7 | no_of_nights | 0.063422 |
| 2 | market_segment_type | 0.061141 |
| 6 | no_of_guests | 0.031970 |
| 0 | required_car_parking_space | 0.006433 |
| 3 | repeated_guest | 0.002462 |
La columna "feature" muestra el nombre de las variables en el conjunto de datos y la columna "importance" muestra el porcentaje de importancia de cada característica en el modelo. Según los resultados, la característica más importante para el modelo es 'lead_time' con un valor de importancia del 38'4%. Esto sugiere que el tiempo de espera entre la reserva y la llegada es un factor crítico en la predicción del modelo. Las siguientes características más importantes son 'date', 'avg_price_per_room' y 'no_of_special_requests', lo que sugiere que la fecha de llegada, el precio promedio de la habitación y el número de solicitudes especiales también son factores importantes. La característica "repeated_guest" es la menos importante en el modelo con una importancia del 0'25%. Las cuatro variables más importantes ('lead_time', 'date', 'avg_price_per_room' y 'no_of_special_requests') acumulan un 83'5% de importancia en la predicción, por lo que los valores que puedan tener estas variables son determinantes a la hora de predecir el resultado de la reserva.
El modelo predictivo que se ha desarrollado anteriormente se utilizará para predecir en diferentes casos que tipos de reserva se cancelarán o no. En este caso, se han planteado estos tres casos diferentes para ver en que funcionamiento tiene el modelo desarrollado:
En el primer caso, se puede inferir que alguien hizo una reserva de hotel para dos huéspedes que se quedarán en la habitación durante cuatro noches. La reserva se realizó con dos meses de antelación (60 días), y la habitación tiene un precio promedio de 85€. También, se puede ver que el cliente hizo una solicitud especial (una solicitud adicional aparte de la reserva de habitación), y que no se requiere un espacio de estacionamiento para el coche. Además, el cliente no ha repetido en este establecimiento y el tipo de mercado está dirigido al sector 'Aviation'. La fecha de la reserva es el 21 de agosto de 2018.
En el segundo caso, alguien hizo una reserva de hotel para un huésped que se quedará en la habitación durante dos noches. La reserva se realizó con un poco menos de un mes de antelación (28 días), y la habitación tiene un precio de 42'5€. También, se puede ver que el cliente no hizo solicitudes especiales y no se requiere un espacio de estacionamiento para el coche. Además, el huésped no ha estado anteriormente en este hotel y el tipo de mercado segmento es 'Complementary'. La reserva es para el 12 de abril de 2018.
En el tercer caso, un cliente hizo una reserva de hotel para dos huéspedes que se quedarán en la habitación durante tres noches. La reserva se realizó con siete meses y medio de antelación (220 días), y la habitación tiene un precio promedio de 101€. También, se puede ver que el cliente no hizo solicitudes especiales y se requiere un espacio de estacionamiento para el coche. Además, el cliente no es un huésped repetido y el tipo de mercado segmento es 'Corporate'. La reserva es para el día 3 de junio de 2017.
En el último, la reserva incluye un requerimiento de estacionamiento, se realizó con 153 días de anticipación a la fecha de llegada y el precio promedio por habitación es de 137'6€. No se realizaron solicitudes especiales y la reserva es para 3 huéspedes durante 1 noche. La reserva fue realizada el 28 de septiembre de 2017.
# Creación de los casos
caso_1 = pd.DataFrame([[0, 60, 0, 0, 85, 1, 2, 4, 20180821]], columns=list(features.columns))
caso_2 = pd.DataFrame([[0, 28, 1, 1, 42.5, 2, 1, 8, 20180412]], columns=list(features.columns))
caso_3 = pd.DataFrame([[1, 220, 2, 0, 101, 0, 2, 3, 20170603]], columns=list(features.columns))
caso_4 = pd.DataFrame([[1, 153, 4, 1, 137.6, 0, 3, 12, 20170928]], columns=list(features.columns))
# Predicción de los casos con el modelo seleccionado
resultado_1 = train_rfc.predict(caso_1)
resultado_2 = train_rfc.predict(caso_2)
resultado_3 = train_rfc.predict(caso_3)
resultado_4 = train_rfc.predict(caso_4)
list_resultado = [resultado_1, resultado_2, resultado_3, resultado_4]
# Visualización de los resultados de las predicciones para cada caso
for n_caso in range(0, len(list_resultado)):
if(list_resultado[n_caso]):
print("En el caso {} no se ha cancelado la reserva".format(n_caso+1))
else:
print("En el caso {} se ha cancelado la reserva".format(n_caso+1))
En el caso 1 no se ha cancelado la reserva En el caso 2 no se ha cancelado la reserva En el caso 3 se ha cancelado la reserva En el caso 4 se ha cancelado la reserva
Viendo los resultados y el análisis de datos que se ha hecho en el proyecto se han sacado varias conclusiones sobre las variables. También, se han visto patrones en las variables por los que una reserva puede ser cancelada o no. Haciendo un repaso a las variables estas son las conclusiones que se han sacado:
Teniendo en cuenta está información de las variables y que las variables que más peso tienen en la predicción son 'lead_time', 'date', 'avg_price_per_room' y 'no_of_special_requests' respectivamente. Estas cuatro variables son determinantes a la hora de decidir el resultado de la predicción, ya que estas cuatro variables son las que más peso tienen en el modelo, lo que hace que las demás variables no tengan tanta repercusión en la predicción. Se ve claramente, en los casos que se han propuesto:
Por lo que, son lógicas las predicciones obtenidas por el modelo. Esto quiere decir que el modelo que se ha desarrollado ha dado un buen rendimiento y el análisis que se ha hecho sobre el conjunto de datos tienen buena precisión, ya que se han cumplido todas las conclusiones que se han sacado en el análisis de datos.
A medida que las preferencias de los clientes y las tendencias de viaje evolucionan, los modelos de predicción que utilizan los hoteles para pronosticar la demanda de habitaciones pueden volverse inexactos y generar errores en las predicciones. Estos cambios pueden tener un impacto significativo en la capacidad del hotel para optimizar su capacidad y maximizar sus ingresos, por lo que es importante monitorear y detectar cualquier signo de concept drift y ajustar los modelos de predicción en consecuencia.
En este apartado, se ha provocado concept drift en el conjunto de datos de diferentes maneras para ver como afecta en el modelo predictivo. Se han utilizado diferentes métodos para generar concept drift artificialmente, drift basado en la distribución del tiempo (Time distribution-based drift) y drift basado en las variables (Feature drift). Después de generar concept drift, se ha detectado mediante diferentes métodos de detección de drift, uno de ellos ha sido usando las funciones de la libreria river.
Para ver como ha afectado el concept drift en el conjunto de datos, se ha representado la variable afectada en un gráfico. En estos gráficos se ha visto claramente las consecuencias de la generación de este efecto. También, se ha visto diferentes tipos de concept drift que hay, ya que los diferentes métodos de generación tienen crean un tipo diferente de concept drift.
Es importante tener en cuenta que la introducción del concept drift en un conjunto de datos real puede ser un reto, ya que requiere una cuidadosa consideración de los factores específicos del dominio que pueden afectar a la distribución subyacente de los datos. Además, es esencial evaluar la eficacia de los métodos de detección y adaptación del concept drift utilizados para asegurarse de que funcionan correctamente.
La función 'detect_drift_ADWIN' toma como entrada un dataframe y devuelve un diccionario que contiene los índices de los cambios (drift) detectados en el dataframe utilizando el algoritmo ADWIN. ADWIN es un algoritmo de detección de cambio adaptativo de la libreria river que monitorea continuamente los datos de entrada y detecta cambios en la distribución subyacente. La función itera a través del dataframe y en cada iteración actualiza el modelo ADWIN con el valor actual. Si se detecta un cambio (drift) en la distribución, se agrega al diccionario la posición y el valor en el dataframe donde se produjo el cambio.
# Concept drift: ADWIN
def detect_drift_ADWIN(df, t):
first, first_drift = True, tuple()
list_drift = {}
adwin = ADWIN()
for i, val in enumerate(df):
_ = adwin.update(val)
if adwin.drift_detected:
list_drift[i] = val
if t < i and first:
first_drift = (i, val)
first = False
return list_drift, first_drift
El algoritmo de PageHinkley se basa en la idea de que, cuando se produce un cambio en los datos, la estadística acumulativa basada en la media o en la varianza también cambiará significativamente. La función PageHinkley monitorea estos cambios mediante la comparación de una medida acumulativa con un umbral adaptativo.
La función 'detect_drift_PH' utiliza la función PageHinkley para analizar los datos y determinar si se ha producido un concept drift. Devuelve un diccionario que contiene los índices y los valores donde se ha detectado un cambio en los datos. Esto se utiliza para identificar y analizar los momentos en los que se producen cambios significativos en los datos, lo que puede requerir acciones como la actualización o adaptación de los modelos de aprendizaje automático.
# Concept drift: PageHinkley
def detect_drift_PH(df, t):
first, first_drift = True, tuple()
list_drift = {}
ph = PageHinkley()
for i, val in enumerate(df):
_ = ph.update(val)
if ph.drift_detected:
list_drift[i] = val
if t < i and first:
first_drift = (i, val)
first = False
return list_drift, first_drift
La función de KSWIN (Kolmogorov-Smirnov Windowing) es utilizado para detectar concept drift en flujos de datos. La función 'detect_drift_KSWIN' utiliza la prueba de Kolmogorov-Smirnov para medir la diferencia entre las distribuciones acumulativas. Mediante una ventana deslizante, se monitoriza continuamente el flujo de datos y se detecta la deriva cuando el estadístico KS supera un umbral predefinido. El método es no paramétrico y puede adaptarse a datos no estacionarios. Sin embargo, tiene limitaciones en situaciones de cambios abruptos o múltiples cambios en los conceptos, y requiere ajuste cuidadoso de la ventana y umbral adecuados.
La función 'detect_drift_KSWIN' utiliza el algoritmo KSWIN para detectar la deriva de conceptos en un dataframe. Se crea un objeto kswin con parámetros predefinidos y se itera sobre los valores del dataframe. Si se detecta un cambio significativo, se registra el índice y el valor correspondiente en un diccionario. Al final, se devuelve el diccionario con la información de las derivas detectadas.
# Concept drift: KSWIN
def detect_drift_KSWIN(df, t):
first, first_drift = True, tuple()
list_drift = {}
kswin = KSWIN()
for i, val in enumerate(df):
_ = kswin.update(val)
if kswin.drift_detected:
list_drift[i] = val
if t < i and first:
first_drift = (i, val)
first = False
return list_drift, first_drift
La función 'display_drift' toma como entrada el diccionario devuelto por la función 'detect_drift' de diferentes tipos de detección y un título, y visualiza gráficamente los cambios detectados en el dataframe. La función traza un gráfico de dispersión con los índices del dataframe en el eje x y los valores en el eje y, lo que permite visualizar las consecuencias de la generación del concept drift.
# Visualización de los datos
def display_drift(list_drift, title):
plt.scatter(list_drift.keys(), list_drift.values())
plt.title(title)
plt.show()
El time distribution-based drift se refiere a los cambios en la distribución de los datos a lo largo del tiempo. En el caso de las reservas online de hoteles, la demanda de habitaciones puede cambiar estacionalmente o en respuesta a eventos locales o nacionales. Como resultado, la distribución de las reservas puede cambiar con el tiempo, lo que a su vez puede afectar la capacidad de los modelos de predecir con precisión la demanda futura.
Este tipo de drift se produce cuando la distribución temporal de los datos cambia con el tiempo. En este caso, se introduce drift en los datos a intervalos regulares de tiempo, lo que simula cambios en la distribución temporal de los datos de reservas de hoteles en línea.
hotels_time_drift = hotels_cleaned.copy()
# Establecer la columna 'date' como índice
hotels_time_drift.set_index('date', inplace=True)
# Definir el número de filas del conjunto de datos
n_rows = len(hotels_time_drift)
# Definir el número de puntos de drift
n_drift_points = 3
# Definir la longitud de cada período de drift
drift_period = n_rows // (n_drift_points + 1)
# Inicializar la magnitud y dirección de la drift
drift_magnitude = 0.5
drift_direction = 1
# Recorrer las filas del conjunto de datos e introducir drift en los intervalos especificados
for i in range(int(n_rows/2), n_rows):
# Determinar si se ha alcanzado un punto de drift
if i % drift_period == 0:
# Invertir la dirección del drift
drift_direction *= -1
# Actualizar la magnitud del drift
drift_magnitude = np.random.uniform(0, 1)
# Introducir la drift añadiendo un valor aleatorio a 'no_of_special_requests'
hotels_time_drift.loc[hotels_time_drift.index[i], 'no_of_special_requests'] += drift_direction * drift_magnitude
# Pausa breve para simular un entorno de flujo de datos en tiempo real
time.sleep(0.005)
Se utiliza una técnica llamada drift basada en la distribución de tiempo para introducir aleatoriamente un cambio en la distribución de los datos de reservas a lo largo del tiempo. El conjunto de datos original se copia en un nuevo conjunto llamado 'hotels_time_drift', y se establece la columna 'date' como índice.
El número de filas del conjunto de datos se almacena en la variable 'n_rows', y se establece el número de puntos de drift en 'n_drift_points'. A continuación, se calcula la longitud de cada período de drift en la variable 'drift_period'. La magnitud y la dirección del drift se inicializan en 'drift_magnitude' y 'drift_direction', respectivamente.
El código recorre las filas del conjunto de datos a partir de la mitad, y en cada iteración, determina si se ha alcanzado un punto de drift utilizando el operador de módulo. Si se ha alcanzado un punto de drift, la dirección del drift se invierte y se actualiza la magnitud del drift. Se introduce la drift en los datos añadiendo un valor aleatorio a la columna 'booking_status', que representa el estado de la reserva. Para simular un entorno de flujo de datos en tiempo real, se introduce una breve pausa entre cada iteración utilizando la función time.sleep().
# Definir las variables iniciales
time_features = hotels_time_drift.loc[:, hotels_time_drift.columns != 'booking_status']
time_target = hotels_time_drift['booking_status']
# Entrenar el modelo con el nuevo conjunto de 'features'
X_train, X_test, y_train, y_test = train_test_split(time_features, time_target, test_size=0.2, random_state=24)
rfc_time_drift = RandomForestClassifier(random_state = 24).fit(X_train, y_train)
# Evaluar el nuevo modelo
time_y_test_pred = rfc_time_drift.predict(X_test)
time_accuracy = accuracy_score(y_test, time_y_test_pred)
print("Precisión inicial de modelo test: {}".format(rfc_test_accuracy))
print("Precisión del modelo drift por intercambio: {}".format(time_accuracy))
Precisión inicial de modelo test: 0.8949689869055824 Precisión del modelo drift por intercambio: 0.8634045485871813
Los resultados indican que la precisión inicial del modelo en el conjunto de prueba es del 89'5%, mientras que la precisión del modelo en el caso de concept drift por tiempo es del 86'34%. Esto sugiere que el modelo puede estar experimentando una disminución en su rendimiento debido a los cambios en los datos a lo largo del tiempo.
list_drift, first_drift = detect_drift_ADWIN(hotels_time_drift['no_of_special_requests'], int(n_rows/2))
print("Cambio detectado en el índice {}, valor de entrada: {}".format(first_drift[0], first_drift[1]))
Cambio detectado en el índice 18207, valor de entrada: 35.0
# Visualización del concept drift con el método 'Time distribution-based drift'
display_drift(list_drift, 'ADWIN: no_of_special_requests')
list_drift, first_drift = detect_drift_PH(hotels_time_drift['no_of_special_requests'], int(n_rows/2))
print("Cambio detectado en el índice {}, valor de entrada: {}".format(first_drift[0], first_drift[1]))
Cambio detectado en el índice 18195, valor de entrada: 35.0
# Visualización del concept drift con el método 'Intercambio de features importantes'
display_drift(list_drift, 'PageHinkley: no_of_special_requests')
list_drift, first_drift = detect_drift_KSWIN(hotels_time_drift['no_of_special_requests'], int(n_rows/2))
print("Cambio detectado en el índice {}, valor de entrada: {}".format(first_drift[0], first_drift[1]))
Cambio detectado en el índice 18204, valor de entrada: 37.0
# Visualización del concept drift con el método 'Time distribution-based drift'
display_drift(list_drift, 'KSWIN: no_of_special_requests')
El código inserta el tipo de drift gradual en la variable 'no_of_special_requests' al introducir pequeñas variaciones aleatorias en la magnitud y dirección de la drift en intervalos regulares de tiempo.
El concept drift gradual se refiere a un cambio gradual en la distribución de los datos a lo largo del tiempo. Este tipo de drift puede ser causado por factores externos como cambios en el comportamiento de los usuarios o la evolución de las tendencias del mercado. A diferencia del sudden drift, el drift gradual no ocurre abruptamente, sino que se produce de manera más lenta y continua.
El drift gradual puede tener un impacto negativo en los modelos de aprendizaje automático que se entrenan con los datos afectados por el drift, ya que los patrones y relaciones que se aprendieron previamente pueden volverse obsoletos.
Al principio, todos los métodos de detección de concept drift detectan valores normales, como valores que han sufrido el efecto. Esto se puede ver en los tres gráficos en los valores de iniciales del conjunto de datos. Aunque, no detecten muchos valores incorrectos estos los diferentes métodos lo detectan. A partir de la mitad del dataset todos los métodos detectan la mayoría de los valores que han sufrido concept drift. Como se puede ver en los gráficos las funciones ADWIN y PageHinkley son los que más valores con concept drift detectan, se puede decir que la función PageHinkley es la función de detección más sensible, ya que detecta la mayoría de valores que han sufrido un gran cambio tanto sean valores normales que valores que han sufrido concept drift, además es el detector que más rápido ha detectado el concept drift en el conjunto de datos. Por otro lado, ADWIN es el método que más tarde ha detectado el drift.
El feature drift se refiere a los cambios en las características de los datos. En el caso de las reservas de hotel en línea, esto puede ocurrir debido a cambios en las preferencias de los clientes, nuevas tendencias en la industria o cambios en las políticas de precios. Por ejemplo, si los clientes comienzan a valorar más ciertas comodidades en las habitaciones, como acceso a Internet de alta velocidad, los modelos que no tengan en cuenta este cambio en las preferencias pueden perder precisión con el tiempo.
En este caso, se ha generado concept drift de dos maneras, la primera se ha generado a partir de la eliminación de las variables más importantes del conjunto de datos original. El segundo método de generación de concept drift es el intercambio de dos features importante. Después de aplicar estos dos métodos al conjunto de datos, se ha representado el efecto de cada método.
En concreto, se eliminan aleatoriamente algunas de las características iniciales del conjunto de datos y se entrena un modelo de clasificación con las características restantes. Luego se evalúa la precisión del modelo y se comprueba si hay una disminución significativa en la precisión en comparación con el modelo original. Si se detecta una disminución significativa en la precisión, se considera que se ha producido un drift en las características del conjunto de datos. Si se detecta un desvío, el proceso de eliminación de características se detiene y se informa al usuario sobre la detección del drift.
# FEATURE DRIFT - Eliminación de 'features' importantes para el modelo a partir de un instante t (date)
hotels_exchange_drift = hotels_cleaned.copy()
# Definir las variables iniciales
initial_features = list(features.columns)
# Definir el instante t
t = hotels_exchange_drift.index[int(n_rows/2)]
# Introducir drift de 'features'
for i in range(10):
np.random.seed(0)
# Elegir al azar una 'feature' para eliminar
feature_to_remove = np.random.choice(initial_features)
new_features = [f for f in initial_features if f != feature_to_remove]
# Entrenar el modelo con el nuevo conjunto de 'features'
X_train, X_test, y_train, y_test = train_test_split(hotels_exchange_drift[new_features], target, test_size=0.2, random_state=24)
drift_rfc = RandomForestClassifier(random_state = 24).fit(X_train, y_train)
# Evaluar la nueva precisión del modelo
drift_y_test_pred = drift_rfc.predict(X_test)
new_accuracy = accuracy_score(y_test, drift_y_test_pred)
# Comprobar concept drift
if new_accuracy < rfc_test_accuracy * 0.9:
print("Concept drift detectado en la iteración {}".format(i))
print("Precisión inicial de modelo test: {}".format(rfc_test_accuracy))
print("Precisión del modelo drift por eliminación: {}".format(new_accuracy))
break
else:
initial_accuracy = new_accuracy
initial_features = new_features
Concept drift detectado en la iteración 3 Precisión inicial de modelo test: 0.8949689869055824 Precisión del modelo drift por eliminación: 0.8031702274293591
La precisión inicial del modelo de prueba fue de 87'69%, lo que indica que el modelo tenía un buen rendimiento en la predicción de los resultados. Sin embargo, en la iteración 4, se detectó un concept drift. En este caso, se detectó mediante la disminución de la precisión del modelo después de la eliminación de una de las características. La nueva precisión del modelo después de introducir el concept drift fue de 78'36%, lo que indica una disminución significativa en el rendimiento del modelo. Esto puede deberse a la eliminación de una característica importante que era crucial para la predicción de los resultados. La detección temprana del concept drift es crucial en el mantenimiento de la precisión del modelo y su capacidad para proporcionar resultados precisos y útiles.
print("Estos son las variables que quedan después de la eliminación: {}".format(new_features))
Estos son las variables que quedan después de la eliminación: ['required_car_parking_space', 'lead_time', 'market_segment_type', 'repeated_guest', 'date']
ADWIN, PageHinkley y KSWIN no son aplicables directamente para detectar concept drift relacionado con la eliminación de variables importantes en un conjunto de datos. Se utilizan otra técnica más adecuada para evaluar y detectar cambios específicos en la eliminación de variables.
Este código realiza un intercambio de características en el conjunto de datos 'hotels_exchange_drift'. El código define dos características importantes, 'lead_time' y 'no_of_special_requests', y las intercambia a partir de un instante de tiempo t. Luego, entrena un modelo de clasificación RandomForestClassifier con el nuevo conjunto de características intercambiadas y evalúa su precisión utilizando el conjunto de prueba.
El intercambio de características en un conjunto de datos puede provocar concept drift, ya que los modelos pueden haber sido entrenados previamente con ciertas características y, por lo tanto, estar optimizados para ellas. Si se intercambian características importantes en el conjunto de datos, el modelo puede verse afectado por este cambio y ya no ser capaz de hacer predicciones precisas.
# FEATURE DRIFT - Intercambiar 2 features importantes para el modelo a partir de un instante t
# Definir las dos 'features' importantes para intercambiar
feature1 = 'lead_time'
feature2 = 'no_of_special_requests'
# Intercambiar los dos features a partir del instante t
temp1, temp2 = hotels_exchange_drift.loc[t:, feature1].copy(), hotels_exchange_drift.loc[t:, feature2].copy()
hotels_exchange_drift.loc[t:, feature1], hotels_exchange_drift.loc[t:, feature2] = temp2, temp1
# Definir las variables iniciales
exchange_features = hotels_exchange_drift.loc[:, hotels_exchange_drift.columns != 'booking_status']
exchange_target = hotels_exchange_drift['booking_status']
# Entrenar el modelo con el nuevo conjunto de 'features'
X_train, X_test, y_train, y_test = train_test_split(exchange_features, exchange_target, test_size=0.2, random_state=24)
rfc_exchange_drift = RandomForestClassifier(random_state = 24).fit(X_train, y_train)
# Evaluar el nuevo modelo
exchange_y_test_pred = rfc_exchange_drift.predict(X_test)
exchange_accuracy = accuracy_score(y_test, exchange_y_test_pred)
print("Precisión inicial de modelo test: {}".format(rfc_test_accuracy))
print("Precisión del modelo drift por intercambio: {}".format(exchange_accuracy))
Precisión inicial de modelo test: 0.8949689869055824 Precisión del modelo drift por intercambio: 0.8970365265334252
Los resultados indican que la precisión del modelo ha disminuido después de que ocurriera el concept drift causado por el intercambio de los valores de las variables 'lead_time' y 'no_of_special_requests'. En particular, la precisión del modelo se ha mantenido a pesar del concept drift esto puede pasar por varias razones. Pero, la razón más probable es que el intercambio de características puede haber afectado la relevancia relativa de cada una en el modelo. Es posible que la característica 'no_of_special_requests' haya demostrado ser más relevante para el nuevo concepto que estás modelando, lo que ha llevado a una mejora en la precisión del modelo al darle más peso o importancia a esta característica.
# Se obtiene la importancia de las diferentes características del modelo
feature_importance = pd.DataFrame({'feature': list(features.columns), 'importance': list(rfc_exchange_drift.feature_importances_)})
feature_importance.sort_values('importance', ascending=False)
| feature | importance | |
|---|---|---|
| 5 | no_of_special_requests | 0.263240 |
| 1 | lead_time | 0.197692 |
| 8 | date | 0.192016 |
| 4 | avg_price_per_room | 0.188993 |
| 7 | no_of_nights | 0.065316 |
| 2 | market_segment_type | 0.052804 |
| 6 | no_of_guests | 0.031300 |
| 0 | required_car_parking_space | 0.006461 |
| 3 | repeated_guest | 0.002179 |
Efectivamente, como se ha comentado anteriormente el intercambio de características ha afectado la relevancia relativa de cada una en el modelo. Esto se puede ver en la tabla que se visualiza aquí arriba. La tabla muestra la importancia relativa de diferentes características en un modelo. "no_of_special_requests" es la característica más importante, seguida de "lead_time", "date" y "avg_price_per_room". Las demás características tienen una importancia relativamente menor. En general, la tabla proporciona información sobre qué características tienen un mayor impacto en las predicciones del modelo. Aunque, la precisión del modelo nuevo no se ha visto afectada, ya que la precisión del modelo original y de este modelo son similares.
list_drift, first_drift = detect_drift_ADWIN(hotels_exchange_drift[feature2], int(n_rows/2))
print("Cambio detectado en el índice {}, valor de entrada: {}".format(first_drift[0], first_drift[1]))
Cambio detectado en el índice 18175, valor de entrada: 18
# Visualización del concept drift con el método 'Intercambio de features importantes'
display_drift(list_drift, 'ADWIN: '+feature2)
list_drift, first_drift = detect_drift_PH(hotels_exchange_drift[feature2], int(n_rows/2))
print("Cambio detectado en el índice {}, valor de entrada: {}".format(first_drift[0], first_drift[1]))
Cambio detectado en el índice 18167, valor de entrada: 69
# Visualización del concept drift con el método 'Intercambio de features importantes'
display_drift(list_drift, 'PageHinkley: '+feature2)
list_drift, first_drift = detect_drift_KSWIN(hotels_exchange_drift[feature2], int(n_rows/2))
print("Cambio detectado en el índice {}, valor de entrada: {}".format(first_drift[0], first_drift[1]))
Cambio detectado en el índice 18170, valor de entrada: 69
# Visualización del concept drift con el método 'Intercambio de features importantes'
display_drift(list_drift, 'KSWIN: '+feature2)
Este cambio repentino en el conjunto de datos es un ejemplo de un concepto de concept drift llamado sudden drift, donde el cambio en el concepto es inesperado y se produce de repente.
Sudden drift, también conocido como concept drift abrupto, se refiere a un tipo de cambio en los datos que ocurre de manera repentina y sin previo aviso. En este tipo de drift, los patrones de los datos cambian de manera brusca en un momento específico, lo que puede afectar el rendimiento de los modelos de aprendizaje automático entrenados previamente.
El concept drift abrupto puede ocurrir cuando hay un intercambio de valores entre dos features, en particular, la variable 'lead_time' y 'no_of_special_requests'. En este caso, los modelos que antes funcionaban bien se ven afectados por la repentina falta de datos, lo que hace que se vuelvan inútiles para su propósito original.
En el método anterior de generación de concept drift se han usado las mismas funciones de detección para ver sus efectos y los cambios que causa. En este caso, las conclusiones son las mismas. La función con mayor sensibilidad de detección de cambio es PageHinkley, en este gráfico se ve todavía más claro las consecuencias del concept drift en la variable 'no_of_special_request'. En este caso también, el método PageHinkley es el que más rápido y ADWIN el más lento detectando el concept drift en el conjunto de datos.
La elección de la estrategia de adaptación en respuesta a un concept drift específico puede variar dependiendo de la naturaleza del drift y los requisitos del problema en cuestión. A continuación, se proporcionan algunas consideraciones generales sobre qué tipo de concept drift puede ser adecuado para cada estrategia de adaptación. Aunque, en los modelos se haya generado artificialmente concept drift, estos no se han visto afectados en la precisión predictiva. Aún así, se ha simulado la adaptación para ver el proceso que hay que seguir en el caso de que el conjunto de datos se vea afectado por el concept drift. Se ha utilizado el código de la esta página web para la adaptación: https://github.com/Western-OC2-Lab/PWPAE-Concept-Drift-Detection-and-Adaptation
Dos enfoques populares son el Adaptive Random Forest (ARF) y el Streaming Random Patches (SRP). Estas son técnicas que se basan en el uso de conjuntos de clasificadores para adaptarse a los cambios en los datos a medida que se producen.
El ARF es una extensión del algoritmo Random Forest que incorpora la capacidad de adaptación al concept drift. Utiliza una combinación de árboles de decisión y una ventana deslizante para mantener un conjunto de clasificadores actualizados. A medida que llegan nuevos datos, el ARF actualiza los clasificadores existentes y agrega nuevos clasificadores para adaptarse a los cambios en la distribución de los datos. Esto permite mantener una buena precisión predictiva incluso en presencia de concept drift.
Por otro lado, el SRP también utiliza un conjunto de clasificadores, pero en lugar de actualizarlos de manera incremental, utiliza una estrategia de bootstrap para generar muestras aleatorias de datos y entrenar clasificadores en cada muestra. Estos clasificadores se combinan posteriormente para realizar predicciones. El enfoque de muestras aleatorias permite capturar diferentes patrones y adaptarse a los cambios en los datos sin requerir actualizaciones continuas.
Ambas estrategias, ARF y SRP, ofrecen soluciones eficaces para lidiar con el concept drift en entornos de aprendizaje automático en tiempo real. Estas técnicas permiten mantener la precisión del modelo a medida que los datos evolucionan, lo que resulta especialmente útil en aplicaciones donde los cambios en los datos son frecuentes y significativos. Al adaptarse a los cambios en la distribución de los datos, estas estrategias brindan una base sólida para tomar decisiones informadas y mantener modelos predictivos precisos en entornos dinámicos.
Es importante destacar que la elección de la estrategia de adaptación también depende de otros factores, como el tipo de modelo utilizado, la disponibilidad de datos históricos y la disponibilidad de recursos computacionales. Cada estrategia tiene sus propias ventajas y desventajas, y la elección correcta dependerá de la naturaleza específica del problema y las necesidades del modelo en tiempo real.
# Definir una función genérica de aprendizaje adaptativo
# El argumento "modelo" significa un algoritmo de aprendizaje adaptativo en línea
def adaptive_learning(model, X_train, y_train, X_test, y_test):
metric = metrics.Accuracy()
i = 0 # Contar el número de puntos de datos evaluados
t = [] # Registrar el número de puntos de datos evaluados
m = [] # Registrar la precisión en tiempo real
yt = [] # Registrar todas las etiquetas verdaderas del conjunto de pruebas
yp = [] # Registrar todas las etiquetas previstas del conjunto de pruebas
# Aprender el conjunto de entrenamiento
for xi1, yi1 in stream.iter_pandas(X_train, y_train):
model.learn_one(xi1,yi1)
# Predecir el conjunto de pruebas
for xi, yi in stream.iter_pandas(X_test, y_test):
y_pred= model.predict_one(xi) # Predecir la muestra de ensayo
model.learn_one(xi,yi) # Aprende la muestra de la prueba
metric = metric.update(yi, y_pred) # Actualizar la precisión en tiempo real
t.append(i)
m.append(metric.get()*100)
yt.append(yi)
yp.append(y_pred)
i = i+1
print("Accuracy: "+str(round(accuracy_score(yt,yp),4)*100)+"%")
print("Precision: "+str(round(precision_score(yt,yp),4)*100)+"%")
print("Recall: "+str(round(recall_score(yt,yp),4)*100)+"%")
print("F1-score: "+str(round(f1_score(yt,yp),4)*100)+"%")
return t, m
# Definir una función numérica que muestre los cambios de precisión en tiempo real
def acc_fig(t, m, name):
plt.rcParams.update({'font.size': 15})
plt.figure(1,figsize=(10,6))
sns.set_style("darkgrid")
plt.clf()
plt.plot(t,m,'-b',label='Avg Accuracy: %.2f%%'%(m[-1]))
plt.legend(loc='best')
plt.title(name, fontsize=15)
plt.xlabel('Número de muestras')
plt.ylabel('Precisión (%)')
plt.draw()
En la evaluación del modelo, se utilizó un conjunto de entrenamiento inicial que representaba el 10% de los datos, mientras que el 90% restante se usó para las pruebas en línea. Cada muestra del conjunto de prueba se empleó primero para evaluar el modelo y luego para entrenar o actualizar el mismo. Debido a la falta de equilibrio en el conjunto de datos, se utilizaron cinco métricas, incluyendo exactitud, precisión, recuperación y puntuación F1, para evaluar el rendimiento de la detección de anomalías en el marco propuesto.
La adaptación para el drift basado en la distribución del tiempo se refiere a las estrategias y técnicas utilizadas para abordar el concept drift en conjuntos de datos en los que la distribución de las características cambia con el tiempo. En este caso, a partir de un instante t, 'no_of_special_requests' ha sufrido las consecuencias del concept drift. Este tipo de drift es común en problemas donde los datos se recopilan en intervalos de tiempo y las relaciones entre las características y la variable objetivo pueden variar a lo largo del tiempo.
En la evaluación, el primer 10% de los datos se utiliza para el entrenamiento inicial del modelo, y el último 90% de los datos se utiliza para las pruebas en línea. En la validación de prueba y entrenamiento, cada muestra de entrada del conjunto de prueba en línea se utiliza primero para probar el modelo de aprendizaje y después para el entrenamiento/actualización del modelo. Dado que el conjunto de datos utilizado no está equilibrado, se utilizan cinco métricas, entre las que se incluyen la exactitud, la precisión, la recuperación y la puntuación f1, para evaluar el rendimiento de la detección de anomalías del marco propuesto.
X = hotels_time_drift.drop(['booking_status'],axis=1)
y = hotels_time_drift['booking_status']
X_train, X_test, y_train, y_test = train_test_split(X,y, train_size = 0.1, test_size = 0.9, random_state = 24)
# Usar el modelo Adaptive Random Forest (ARF) con el detector ADWIN
model1_1 = forest.ARFClassifier(n_models = 3, drift_detector = ADWIN()) # Definir el modelo
t, m1_1 = adaptive_learning(model1_1, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m1_1, "ARF-ADWIN model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 78.27% Precision: 80.88% Recall: 88.68% F1-score: 84.6%
# Usar el modelo Adaptive Random Forest (ARF) con el detector PageHinkley
model1_2 = forest.ARFClassifier(n_models = 3, drift_detector = PageHinkley()) # Definir el modelo
t, m1_2 = adaptive_learning(model1_2, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m1_2, "ARF-PageHinkley model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 76.21% Precision: 78.78% Recall: 88.48% F1-score: 83.35000000000001%
# Usar el modelo Adaptive Random Forest (ARF) con el detector KSWIN
model1_3 = forest.ARFClassifier(n_models = 3, drift_detector = KSWIN()) # Definir el modelo
t, m1_3 = adaptive_learning(model1_3, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m1_3, "ARF-KSWIN model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 78.88% Precision: 80.95% Recall: 89.74% F1-score: 85.11999999999999%
# Usar el modelo Streaming Random Patches (SRP) con detector ADWIN
model2_1 = ensemble.SRPClassifier(n_models = 3, drift_detector = ADWIN()) # Definir el modelo
t, m2_1 = adaptive_learning(model2_1, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m2_1, "SRP-ADWIN model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 79.27% Precision: 80.93% Recall: 90.52% F1-score: 85.46000000000001%
# Usar el modelo Streaming Random Patches (SRP) con detector PageHinkley
model2_2 = ensemble.SRPClassifier(n_models = 3, drift_detector = PageHinkley()) # Definir el modelo
t, m2_2 = adaptive_learning(model2_2, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m2_2, "SRP-PageHinkley model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 75.83% Precision: 77.99000000000001% Recall: 89.28% F1-score: 83.25%
# Usar el modelo Streaming Random Patches (SRP) con detector KSWIN
model2_3 = ensemble.SRPClassifier(n_models = 3, drift_detector = KSWIN()) # Definir el modelo
t, m2_3 = adaptive_learning(model2_3, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m2_3, "SRP-KSWIN model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 79.44% Precision: 81.15% Recall: 90.46% F1-score: 85.55%
# Dibuja una figura completa para comparar el rendimiento de todos los modelos
plt.rcParams.update({'font.size': 30})
plt.figure(1,figsize=(24,15))
sns.set_style("darkgrid")
plt.clf()
# Visualiza el cambio de precisión de cada modelo
plt.plot(t,m1_1,'-g', label = 'ARP-ADWIN model, Avg Accuracy: %.2f%%'%(m1_1[-1]))
plt.plot(t,m1_2,'orange', label = 'ARP-PageHinkley model, Avg Accuracy: %.2f%%'%(m1_2[-1]))
plt.plot(t,m1_3,'-b', label = 'ARP-KSWIN model, Avg Accuracy: %.2f%%'%(m1_3[-1]))
plt.plot(t,m2_1,'magenta', label = 'SRP-ADWIN model, Avg Accuracy: %.2f%%'%(m2_1[-1]))
plt.plot(t,m2_2,'-r', label = 'SRP-PageHinkley model, Avg Accuracy: %.2f%%'%(m2_2[-1]))
plt.plot(t,m2_3,'brown', label= 'SRP-KSWIN model, Avg Accuracy: %.2f%%'%(m2_3[-1]))
plt.legend(loc='lower right')
plt.ylim(68, 85)
plt.xlabel('Número de muestras')
plt.ylabel('Precisión (%)')
plt.draw()
Todos los resultados obtenidos en este apartado son parecidos, hay un umbral de 5% entre el mejor modelo de adaptación y el peor. Aún así, el peor modelo es el del detector de concept drift PageHinkley. En cualquiera de los dos casos, los modelos generados con PageHinkley son los peores. Por otro lado, los mejores resultados han sido obtenidos por las funciones SRP-KSWIN y ARP-ADWIN.
En este caso, se siguen los mismos pasos que en el apartado anterior, aunque para este proceso se utiliza el conjunto de datos que ha sufrido las consecuencias del drift por el cambio en las características.
X = hotels_exchange_drift.drop(['booking_status'], axis=1)
y = hotels_exchange_drift['booking_status']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size = 0.1, test_size = 0.9, random_state = 24)
# Usar el modelo Adaptive Random Forest (ARF) con el detector ADWIN
model3_1 = forest.ARFClassifier(n_models = 3, drift_detector = ADWIN()) # Definir el modelo
t, m3_1 = adaptive_learning(model3_1, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m3_1, "ARF-ADWIN model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 80.32000000000001% Precision: 82.78% Recall: 89.33% F1-score: 85.92999999999999%
# Usar el modelo Adaptive Random Forest (ARF) con el detector PageHinkley
model3_2 = forest.ARFClassifier(n_models = 3, drift_detector = PageHinkley()) # Definir el modelo
t, m3_2 = adaptive_learning(model3_2, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m3_2, "ARF-PageHinkley model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 77.13% Precision: 80.11% Recall: 87.81% F1-score: 83.78999999999999%
# Usar el modelo Adaptive Random Forest (ARF) con el detector KSWIN
model3_3 = forest.ARFClassifier(n_models = 3, drift_detector = KSWIN()) # Definir el modelo
t, m3_3 = adaptive_learning(model3_3, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m3_3, "ARF-KSWIN model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 82.46% Precision: 84.33% Recall: 90.8% F1-score: 87.45%
Estos resultados representan las métricas de rendimiento de diferentes estrategias de adaptación utilizando el modelo ARF con diferentes detectores de drift (ADWIN, PageHinkley y KSWIN). Las métricas indican la eficacia de cada modelo en términos de exactitud, precisión, recuperación y puntuación F1.
Estos resultados ofrecen una visión general del rendimiento de cada modelo. Los valores más altos de exactitud, precisión, recuperación y puntuación F1 indican un mejor rendimiento. Comparando los tres modelos, el modelo ARF-KSWIN obtuvo la mayor exactitud, precisión, recuperación y puntuación F1, seguido del modelo ARF-ADWIN. El modelo ARF-PageHinkley tuvo un rendimiento ligeramente inferior en términos de estas métricas.
# Usar el modelo Streaming Random Patches (SRP) con detector KSWIN
model4_1 = ensemble.SRPClassifier(n_models = 3, drift_detector = ADWIN()) # Definir el modelo
t, m4_1 = adaptive_learning(model4_1, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m4_1, "SRP-ADWIN model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 78.46% Precision: 80.07% Recall: 90.53% F1-score: 84.98%
# Usar el modelo Streaming Random Patches (SRP) con detector KSWIN
model4_2 = ensemble.SRPClassifier(n_models = 3, drift_detector = PageHinkley()) # Definir el modelo
t, m4_2 = adaptive_learning(model4_2, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m4_2, "SRP-PageHinkley model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 75.8% Precision: 78.03% Recall: 89.13% F1-score: 83.21%
# Usar el modelo Streaming Random Patches (SRP) con detector KSWIN
model4_3 = ensemble.SRPClassifier(n_models = 3, drift_detector = KSWIN()) # Definir el modelo
t, m4_3 = adaptive_learning(model4_3, X_train, y_train, X_test, y_test) # Aprender el modelo en el dataset
acc_fig(t, m4_3, "SRP-KSWIN model") # Dibuja la figura de cómo cambia la precisión en tiempo real con el número de muestras
Accuracy: 80.76% Precision: 82.22% Recall: 91.11% F1-score: 86.44%
Los resultados son parecidos al apartado anterior, ya que comparando los tres modelos, el modelo SRP-KSWIN obtuvo la mayor exactitud, precisión, recuperación y puntuación F1. Sin embargo, en este caso el modelo SRP-PageHinkley ha obtenido mejores resultados que el modelo SRP-ADWIN que obtuvo un rendimiento ligeramente inferior en estas métricas.
# Dibuja una figura completa para comparar el rendimiento de todos los modelos
plt.rcParams.update({'font.size': 30})
plt.figure(1,figsize=(24,15))
sns.set_style("darkgrid")
plt.clf()
# Visualiza el cambio de precisión de cada modelo
plt.plot(t,m3_1,'-g', label = 'ARP-ADWIN model, Avg Accuracy: %.2f%%'%(m3_1[-1]))
plt.plot(t,m3_2,'orange', label = 'ARP-PageHinkley model, Avg Accuracy: %.2f%%'%(m3_2[-1]))
plt.plot(t,m3_3,'-b', label = 'ARP-KSWIN model, Avg Accuracy: %.2f%%'%(m3_3[-1]))
plt.plot(t,m4_1,'magenta',label = 'SRP-ADWIN model, Avg Accuracy: %.2f%%'%(m4_1[-1]))
plt.plot(t, m4_2,'-r', label = 'SRP-PageHinkley model, Avg Accuracy: %.2f%%'%(m4_2[-1]))
plt.plot(t,m4_3,'brown', label= 'SRP-KSWIN model, Avg Accuracy: %.2f%%'%(m4_3[-1]))
plt.legend(loc='lower right')
plt.ylim(68, 85)
plt.xlabel('Número de muestras')
plt.ylabel('Precisión (%)')
plt.draw()
En conclusión, viendo los resultados obtenidos en los dos apartados previos, los mejores resultados se han obtenido mediante el detector KSWIN. Como se puede ver en el gráfico, el método KSWIN ha obtenido el valor de precisión más alto comparado con los demás métodos en cualquiera de los casos, tanto si se utiliza un modelo ARP o SRP. Por el contrario, el detector PageHinkley obtiene los peores resultados de lejos comparado con los demás. El peor caso es el de ARP-PageHinkley que baja la precisión un 5% comparado con el mejor modelo conseguido por ARP-KSWIN.
El historial de cancelaciones y reservas previas juega un papel importante en la condición de los huéspedes habituales. Además, se ha observado que el tipo de habitación está estrechamente relacionado con la capacidad de alojamiento, siendo las habitaciones más grandes ideales para grupos numerosos. Esto se refleja en el precio promedio por habitación, donde las opciones más amplias y con mayor capacidad tienden a ser más costosas. Por otro lado, el tiempo de anticipación en la realización de una reserva puede influir en su probabilidad de cancelación, siendo las reservas realizadas con mayor antelación más propensas a ser canceladas. Por último, las solicitudes especiales de los huéspedes también desempeñan un papel significativo, ya que aquellos con más solicitudes especiales muestran una menor probabilidad de cancelación. Estos factores son importantes consideraciones para garantizar una experiencia satisfactoria para los huéspedes y una planificación eficiente en la industria hotelera.
El modelo DecisionTreeClassifier muestra una precisión aceptable en general, pero tiene dificultades en la identificación de casos positivos. Se recomienda mejorar su capacidad para clasificar correctamente estos casos. El modelo RandomForestClassifier ha obtenido una buena precisión en el conjunto de prueba, lo que indica que clasifica correctamente la mayoría de las instancias. Además, presenta un bajo número de falsos positivos y falsos negativos, lo que demuestra un rendimiento efectivo en la clasificación de los datos.El modelo LogisticRegression logra una precisión razonable en la predicción del conjunto de prueba, pero muestra algunos errores de clasificación. Se sugiere mejorar su capacidad para evitar falsos positivos y falsos negativos. En resumen, los resultados indican que el modelo RandomForestClassifier es el que muestra el mejor rendimiento en términos de precisión, baja tasa de errores de clasificación y área bajo la curva ROC. Por lo tanto, se recomienda utilizar este modelo para realizar las predicciones, ya que ha demostrado tener los mejores resultados en comparación con los otros modelos evaluados.
El análisis de los datos revela patrones interesantes sobre las variables que impactan la experiencia del huésped en un hotel. Una de las conclusiones clave es que el historial de cancelaciones y reservas previas juega un papel importante en la probabilidad de que un huésped sea un cliente recurrente. Aquellos con un historial de cancelaciones tienen menos probabilidades de repetir, mientras que aquellos con reservas no canceladas son más propensos a volver y convertirse en clientes leales. Otro hallazgo significativo es la relación entre el tipo de habitación y el número de huéspedes. Se observa que las habitaciones más grandes tienen la capacidad de alojar a más personas, lo cual está estrechamente relacionado con las preferencias de los huéspedes y la disponibilidad de espacio. Esta información es valiosa para la gestión del hotel, ya que les permite ofrecer opciones de alojamiento adecuadas para diferentes grupos de huéspedes. El precio promedio por habitación también se ve influenciado por el tipo de habitación y el número de huéspedes. Las habitaciones más grandes y con mayor capacidad suelen tener precios más altos debido a la mayor comodidad y espacio que ofrecen. Esta relación entre el precio y el tamaño de la habitación es comprensible y brinda una base sólida para la fijación de precios en el hotel. El tiempo de antelación en la realización de una reserva es otro factor relevante que afecta la probabilidad de cancelación. Las reservas hechas con mucha antelación tienen una mayor probabilidad de ser canceladas, lo cual puede atribuirse a cambios en los planes de los huéspedes o a la incertidumbre a largo plazo. Esta información es valiosa para la gestión de la disponibilidad de habitaciones y para la planificación de recursos en el hotel. Por último, las solicitudes especiales realizadas por los huéspedes también juegan un papel importante en la probabilidad de cancelación. Aquellos que presentan un mayor número de solicitudes especiales tienen una menor probabilidad de cancelar su reserva, lo que indica un mayor compromiso por parte de los huéspedes con su estancia en el hotel.
Los cambios en las preferencias y tendencias de los clientes pueden afectar los modelos de predicción de demanda hotelera, generando errores en las predicciones. Se han utilizado diferentes métodos para provocar concept drift en los datos y se ha detectado utilizando métodos de detección de drift. El impacto del concept drift se ha observado en gráficos que representan la variable afectada. Existen diferentes tipos de concept drift dependiendo de los métodos utilizados para generarlo, el método que mayor efecto negativo ha creado en la precisión del modelo ha sido la eliminación de variables importante. Al borrar las variables más importantes en el conjunto de datos, este pierde información valiosa de cara al desarrollo del modelo y de su precisión, por lo que las consecuencias han sido mayores que en los otros dos métodos aplicados.
A medida que las preferencias de los clientes y las tendencias de viaje evolucionan, es crucial que los hoteles puedan pronosticar con precisión la demanda de habitaciones. Sin embargo, los modelos de predicción pueden volverse inexactos debido al concept drift, lo que puede afectar la capacidad del hotel para optimizar su capacidad y maximizar sus ingresos. En este estudio, se ha introducido deliberadamente concept drift gradual en la variable 'no_of_special_requests' mediante pequeñas variaciones aleatorias en intervalos regulares de tiempo. Se ha observado que este tipo de drift, que implica un cambio lento y continuo en la distribución de los datos a lo largo del tiempo, puede tener un impacto negativo en los modelos de aprendizaje automático. Los patrones y relaciones previamente aprendidos se vuelven obsoletos, lo que limita la precisión de las predicciones. Al analizar los resultados, se han encontrado que los métodos de detección de concept drift fueron capaces de identificar los valores afectados, pero inicialmente también detectaron valores normales. A medida que se ha avanzado en el conjunto de datos, los métodos fueron más efectivos en detectar la mayoría de los valores con concept drift. Se ha destacado que la función PageHinkley fue la más sensible, detectando tanto los valores normales como los afectados por el drift, y también fue la más rápida en detectar los cambios en el conjunto de datos. Por otro lado, ADWIN fue más lento en detectar el concept drift. Se ha observado un cambio repentino y abrupto en el conjunto de datos, lo que se conoce como sudden drift. Este tipo de drift puede ocurrir cuando hay un intercambio de valores entre dos variables, como 'lead_time' y 'no_of_special_requests'. En nuestro caso, este cambio repentino afectó el rendimiento de los modelos previamente entrenados, ya que los datos anteriores se volvieron inútiles para su propósito original. Estos hallazgos resaltan la importancia de monitorear y adaptarse al concept drift para mantener la precisión de los modelos de predicción. Es esencial utilizar métodos de detección adecuados, como PageHinkley, que sean sensibles y rápidos en identificar los cambios en los datos. De esta manera, los hoteles podrán ajustar sus estrategias y tomar decisiones informadas para maximizar su capacidad y satisfacer las demandas cambiantes de los clientes en un entorno dinámico.
La adaptación para el drift basado en la distribución del tiempo se refiere a las estrategias y técnicas utilizadas para abordar el concept drift en conjuntos de datos donde la distribución de las características cambia a lo largo del tiempo. En este caso particular, se observó un concept drift en la variable 'no_of_special_requests' a partir de un momento determinado. Este tipo de drift es común en problemas donde los datos se recopilan en intervalos de tiempo y las relaciones entre las características y la variable objetivo pueden variar con el tiempo. Los resultados obtenidos mostraron que, en general, existe una diferencia de aproximadamente un 5% entre el mejor y el peor modelo de adaptación. En particular, se observó que el peor rendimiento se asociaba con el modelo ARF-PageHinkley, mientras que los mejores resultados se lograron con las funciones SRP-KSWIN y ARP-ADWIN. Al comparar los tres modelos de adaptación, se encontró que el modelo ARF-KSWIN obtuvo los valores más altos en todas las métricas de evaluación, incluyendo exactitud, precisión, recuperación y puntuación F1, seguido por el modelo ARF-ADWIN. Por otro lado, el rendimiento del modelo ARF-PageHinkley fue ligeramente inferior en comparación con los otros dos modelos. En resumen, los resultados revelaron que el detector KSWIN fue el más efectivo para abordar el concept drift en este escenario, mostrando una alta precisión tanto en los modelos ARP como SRP. En contraste, el detector PageHinkley presentó un rendimiento deficiente en comparación con los demás detectores. Específicamente, el modelo ARP-PageHinkley experimentó una disminución del 5% en la precisión en comparación con el mejor modelo logrado con ARP-KSWIN. Estos hallazgos resaltan la importancia de elegir adecuadamente los métodos de adaptación y detectores de drift para lograr un rendimiento óptimo en la detección y manejo del concept drift en conjuntos de datos basados en la distribución del tiempo.